'use strict';

(function (globals){
	/*
	 * global variables
	 */
	globals.__last_id__ = 1; 						/* used as object id (non-standard) */
	globals.__modules__ = {}; 						/* loaded modules (non-standard) */
	globals.__builtins__ = {}; 						/* builtins module */
	globals.__modules__['builtins'] = __builtins__;	/* register loaded module */
	
	/*
	 * object and type classes
	 */
	globals.builtins = globals.__builtins__;		/* inuitive global variable for builtins module */
	var builtins = globals.builtins;				/* inuitive local variable for builtins module */
	builtins.object = {}; 							/* object class */
	builtins.type = {}; 							/* type class */
	
	/*
	 * object class
	 */
	builtins.object.__class__ = builtins.type;
	builtins.object.__name__ = 'object';
	builtins.object.__bases__ = [];
	builtins.object.__dict__ = builtins.object;
	builtins.object.__module__ = 'builtins';
	builtins.object.__mro__ = [builtins.object];
	builtins.object.__id__ = __last_id__++;
	
	/*
	 * type class
	 */
	builtins.type.__class__ = builtins.type;
	builtins.type.__name__ = 'type';
	builtins.type.__bases__ = [builtins.object];
	builtins.type.__dict__ = builtins.type;
	builtins.type.__module__ = 'builtins';
	builtins.type.__mro__ = [builtins.type, builtins.object];
	builtins.type.__id__ = __last_id__++;
	
	/*
	 * module class
	 */
	builtins.module = {};
	builtins.module.__class__ = builtins.type;
	builtins.module.__name__ = 'module';
	builtins.module.__bases__ = [builtins.object];
	builtins.module.__dict__ = builtins.module;
	builtins.module.__module__ = 'builtins';
	builtins.module.__mro__ = [builtins.module, builtins.object];
	builtins.module.__id__ = __last_id__++;
	
	/*
	 * __main__ module
	 */
	globals.__class__ = builtins.module;
	globals.__name__ = '__main__';
	globals.__package__ = null;
	globals.__dict__ = globals;
	globals.__id__ = globals.__last_id__++;
	
	/*
	 * __builtins__ module
	 */
	builtins.__class__ = builtins.module;
	builtins.__name__ = 'builtins';
	builtins.__package__ = null;
	builtins.__dict__ = builtins;
	builtins.__id__ = globals.__last_id__++;
	
	/*
	 * builtins functions
	 */
	builtins.getclass = function(args, kwargs){
		/*
		 * return: type
		 * throw: TypeError
		 */
		/*
		 * return: type or undefined
		 */
		var obj = args[0]; /* any object including "undefined" */
		var cls;
		var jscls;
		
		/* py class */
		try {
			cls = obj.__class__;
			if (cls !== undefined) return cls;
		} catch(e){
			/* js exception */
		}
		
		/* js class */
		jscls = typeof obj;
		
		switch (jscls){
			case 'boolean':
				cls = builtins.bool;
				break;
			case 'number':
				if (obj === +obj && obj === (obj | 0)){
					cls = builtins.int;
				} else if (obj === +obj && obj !== (obj | 0)){
					cls = builtins.float;
				}
				
				break;
			case 'object':
				if (obj === null){
					cls = builtins.NoneType;
				} else if (Array.isArray(obj)){
					if (Object.isSealed(obj)){
						cls = builtins.tuple;
					} else {
						cls = builtins.list;
					}
				} else {
					cls = builtins.dict;
				}
				
				break;
			case 'string':
				cls = builtins.str;
				break;
			case 'function':
				cls = builtins.FunctionType;
				break;
			default:
				cls = builtins.NoneType;
		}
		
		return cls;
	};
	
	builtins.getmro = function(args, kwargs){
		/*
		 * return: list
		 * throw: TypeError
		 */
		var obj = args[0];
		var mro;
		var cls;
		
		if (obj === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'wrong instance']);
		}
		
		/* py class */
		try {
			mro = obj.__mro__;
			if (mro !== undefined) return mro;
		} catch (e){
			/* js exception */
		}
		
		/* js class */
		cls = builtins.getclass([obj]);
		
		if (cls === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'wrong mro object']);
		}
		
		try {
			mro = cls.__mro__;
		} catch (e){
			throw builtins.type.__call__([builtins.TypeError, 'wrong mro object']);
		}
		
		return mro;
	};
	
	builtins.isinstance = function (args, kwargs){
		/*
		 * return: bool
		 * throw: TypeError
		 */
		var obj = args[0];
		var cls = args[1];
		var obj_cls;
		var obj_cls_mro;
		var res;
		
		if (obj === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'wrong instance']);
		}
		
		if (cls === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'wrong class']);
		}
		
		obj_cls = builtins.getclass([obj]);
		obj_cls_mro = builtins.getmro([obj_cls]);
		res = false;
		
		for (var i = 0; i < obj_cls_mro.length; i++){
			if (cls == obj_cls_mro[i]){
				res = true;
				break;
			}
		}
		
		return res;
	};
	
	builtins.issubclass = function (args, kwargs){
		/*
		 * return: bool
		 * throw: TypeError
		 */
		var c = args[0];
		var cls = args[1];
		var cls_mro;
		var res;
		
		if (c === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'wrong first class']);
		}
		
		if (cls === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'wrong second class']);
		}
		
		cls_mro = builtins.getmro([cls]);
		res = false;
		
		for (var i = 0; i < cls_mro.length; i++){
			if (c == cls_mro[i]){
				res = true;
				break;
			}
		}
		
		return res;
	};
	
	builtins.setattr = function (args, kwargs){
		var obj = args[0];
		// FIXME: implement
	};
	
	builtins.getattr = function (args, kwargs){
		/*
		 * return: any object
		 * throw: TypeError or AttributeError
		 */
		var obj = args[0];
		var cls = builtins.getclass([obj]);
		var cls_mro;
		var cls_mro_cls;
		var cls_mro_cls2;
		var getattribute;
		var getattr;
		var value;
		
		cls_mro = builtins.getmro([cls]);
		
		for (var i = 0; i < cls_mro.length; i++){
			cls_mro_cls = cls_mro[i];
			getattribute = cls_mro_cls.__getattribute__;
			if (getattribute !== undefined) break;
		}
		
		try {
			value = getattribute(args, kwargs);
		} catch (e){
			if (!builtins.isinstance([e, builtins.AttributeError])) throw e;
			
			for (var i = 0; i < cls_mro.length; i++){
				cls_mro_cls = cls_mro[i];
				getattr = cls_mro_cls.__getattr__;
				if (getattr !== undefined) break;
			}
			
			value = getattr(args, kwargs);
		}
		
		if (value === undefined){
			throw builtins.type.__call__([builtins.AttributeError, 'missing methods "__getattribute__" and/or "__getattr__" so "' + attr + '" cannot be found']);
		}
		
		return value;
	};
	
	builtins.delattr = function (args, kwargs){
		var obj = args[0];
		// FIXME: implement
	};
	
	builtins.hasattr = function (args, kwargs){
		var obj = args[0];
		var res;
		// FIXME: implement
		return res;
	};
	
	builtins.call = function (args, kwargs){
		/*
		 * return: any object
		 * throw: TypeError
		 */
		var obj = args[0];
		var cls;
		var cls_mro;
		var cls_mro_cls;
		var call;
		var res;
		
		if (obj === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'wrong callable instance']);
		}
		
		cls = builtins.getclass([obj]);
		cls_mro = builtins.getmro([cls]);
		
		for (var i = 0; i < cls_mro.length; i++){
			cls_mro_cls = cls_mro[i];
			call = cls_mro_cls.__call__;
			if (call !== undefined) break;
		}
		
		if (call === undefined){
			throw builtins.type.__call__([builtins.TypeError, 'object is not callable']);
		}
		
		res = call(args, kwargs);
		return res;
	};
	
	builtins.globals = function (args, kwargs){
		/* FIXME */
		// throw builtins.call([builtins.NotImplementedError, '']);
	};
	
	builtins.locals = function (args, kwargs){
		/* FIXME */
		// throw builtins.call([builtins.NotImplementedError, '']);
	};
	
	builtins.iter = function (args, kwargs){
		/*
		 * return: int
		 * throw: AttributeError
		 */
		var obj = args[0];
		var cls = builtins.getclass([obj]);
		var func = builtins.getattr([cls, '__iter__']);
		var res = builtins.call([func].concat(args), kwargs);
		return res;
	};
	
	builtins.next = function (args, kwargs){
		/*
		 * return: int
		 * throw: AttributeError
		 */
		var obj = args[0];
		var cls = builtins.getclass([obj]);
		var func = builtins.getattr([cls, '__next__']);
		var res = builtins.call([func].concat(args), kwargs);
		return res;
	};
	
	builtins.len = function (args, kwargs){
		/*
		 * return: int
		 * throw: AttributeError
		 */
		var obj = args[0];
		var cls = builtins.getclass([obj]);
		var func = builtins.getattr([cls, '__len__']);
		var res = builtins.call([func].concat(args), kwargs);
		return res;
	};
	
	builtins.map = function (args, kwargs){
		/*
		 * return: list
		 * throw: TypeError
		 */
		var func = args[0];
		var seqs = args.slice(1);
		var seq = null;
		var item = null;
		var n = null;
		var res = [];
		
		for (var i = 0; i < seqs.length; i++){
			seq = seqs[i];
			
			for (var j = 0; j < seq.length; j++){
				item = seq[j];
				n = builtins.call([func, item]);
				res.push(n);
			}
		}
		
		return res;
	};
	
	builtins.id = function (args, kwargs){
		/*
		 * return: int
		 * throw: AttributeError
		 */
		var obj = args[0];
		return builtins.getattr([obj, '__id__']);
	};
	
	builtins.hash = function (args, kwargs){
		/*
		 * return: int
		 * throw: AttributeError
		 */
		var obj = args[0];
		var cls = builtins.getclass([obj]);
		var func = builtins.getattr([cls, '__hash__']);
		var res = builtins.call([func].concat(args), kwargs);
		return res;
	};
	
	builtins.repr = function (args, kwargs){
		/*
		 * return: str
		 * throw: AttributeError
		 */
		var obj = args[0];
		var cls = builtins.getclass([obj]);
		var func = builtins.getattr([cls, '__repr__']);
		var res = builtins.call([func].concat(args), kwargs);
		return res;
	};
	
	builtins.print = function (args, kwargs){
		/*
		 * return: None
		 * throw: AttributeError
		 */
		var reprs = [];
		
		for (var i = 0; i < args.length; i++){
			reprs.push(
				builtins.repr([args[i]])
			);
		}
		
		console.log(reprs.join(' '));
	};
	
	/*
	 * object class
	 */
	builtins.object.__new__ = function (args, kwargs){
		/*
		 * return: instance of 'cls'
		 */
		var cls = args[0];
		var self = {};
		self.__class__ = cls;
		self.__dict__ = self;
		self.__id__ = __last_id__++;
		return self;
	};
	
	builtins.object.__init__ = function (args, kwargs){
		/* return: None */
	};
	
	builtins.object.__del__ = function (args, kwargs){
		/* return: None */
	};
	
	builtins.object.__hash__ = function (args, kwargs){
		/* return: int */
		var self = args[0];
		return self.__id__;
	};
	
	builtins.object.__str__ = function (args, kwargs){
		/*
		 * return: str
		 * throw: TypeError
		 */
		var self = args[0];
		var cls = builtins.getclass([self]);
		return '<' + cls.__name__  + ' object at ' + self.__id__.toString() + '>';
	};
	
	builtins.object.__repr__ = function (args, kwargs){
		/*
		 * return: str
		 * throw: TypeError
		 */
		var self = args[0];
		var cls = builtins.getclass([self]);
		var str = builtins.getattr([cls, '__str__']);
		return str(args, kwargs);
	};
	
	builtins.object.__setattr__ = function (args, kwargs){
		/* return: None */
		var self = args[0];
		var attr = args[1];
		var value = args[2];
		self[attr] = value;
	};
	
	builtins.object.__getattribute__ = function (args, kwargs){
		var self = args[0];
		var attr = args[1];
		var cls;
		var cls_mro;
		var cls_mro_cls;
		var value;
		var value_cls;
		var value_cls_mro;
		var value_cls_mro_cls;
		var value_cls_mro_cls_get;
		
		cls = builtins.getclass([self]);
		cls_mro = builtins.getmro([cls]);
		
		for (var i = 0; i < cls_mro.length; i++){
			cls_mro_cls = cls_mro[i];
			value = cls_mro_cls[attr];
			if (value !== undefined) break;
		}
		
		if (value === undefined){
			value = self[attr];
		}
		
		if (value === undefined){
			throw builtins.type.__call__([builtins.AttributeError, 'missing attribute "' + attr + '"']);
		}
		
		value_cls = builtins.getclass([value]);
		value_cls_mro = builtins.getmro([value_cls]);
		
		for (var i = 0; i < value_cls_mro.length; i++){
			value_cls_mro_cls = value_cls_mro[i];
			value_cls_mro_cls_get = value_cls_mro_cls.__get__;
			
			if (value_cls_mro_cls_get === undefined){
				continue;
			} else {
				value = value_cls_mro_cls_get([value, self, cls]);
				break;
			}
		}
		
		if (value === undefined){
			throw builtins.type.__call__([builtins.AttributeError, 'missing attribute "' + attr + '"']);
		}
		
		return value;
	}
	
	builtins.object.__delattr__ = function (args, kwargs){
		/*
		 * return: None
		 * throw: AttributeError
		 */
		var self = args[0];
		var attr = args[1];
		
		try {
			delete self[attr];
		} catch (e){
			throw builtins.type.__call__([builtins.AttributeError, 'missing attribute "' + attr + '"']);
		}
	};
	
	/*
	 * type class
	 */
	builtins.type.__new__ = function (args, kwargs){
		/*
		 * return: instance of 'type'
		 */
		var mcs = args[0];
		var name = args[1];
		var bases = args[2];
		var dict = args[3];
		var cls = builtins.object.__new__([mcs]);
		
		/* copy all items from dict to cls dict */
		for (var key in dict) cls[key] = dict[key];
		
		/* ensure object class in bases */
		if (bases.indexOf(builtins.object) === -1) bases.push(builtins.object);
		
		/* standard attributes of 'type' instance */
		cls.__name__ = name;
		cls.__bases__ = bases;
		cls.__dict__ = cls;
		cls.__module__ = __name__;
		cls.__mro__ = builtins.type.mro([cls]);
		return cls;
	};
	
	builtins.type.__getattribute__ = function (args, kwargs){
		/*
		 * return: any object
		 * throw: AttributeError
		 */
		var cls = args[0];
		var attr = args[1];
		var cls_mro;
		var cls_mro_cls;
		var value;
		var value_cls;
		var value_cls_mro;
		var value_cls_mro_cls;
		var value_cls_mro_cls_get;
		
		cls_mro = builtins.getmro([cls]);
		
		for (var i = 0; i < cls_mro.length; i++){
			cls_mro_cls = cls_mro[i];
			value = cls_mro_cls[attr];
			if (value !== undefined) break;
		}
		
		if (value === undefined){
			throw builtins.type.__call__([builtins.AttributeError, 'missing attribute "' + attr + '"']);
		}
		
		value_cls = builtins.getclass([value]);
		value_cls_mro = value_cls.__mro__;
		
		for (var i = 0; i < value_cls_mro.length; i++){
			value_cls_mro_cls = value_cls_mro[i];
			value_cls_mro_cls_get = value_cls_mro_cls.__get__;
			
			if (value_cls_mro_cls_get !== undefined){
				value = value_cls_mro_cls_get([value, null, cls]);
				break;
			}
		}
		
		if (value === undefined){
			throw builtins.type.__call__([builtins.AttributeError, 'missing attribute "' + attr + '"']);
		}
		
		return value;
	};
	
	builtins.type.__str__ = function (args, kwargs){
		/* return: str */
		var cls = args[0];
		return '<class \'' + cls.__module__ + '.' + cls.__name__ + '\'>';
	};
	
	builtins.type.__call__ = function (args, kwargs){
		var cls = args[0];
		var cls_mro = builtins.getmro([cls]);
		var cls_mro_cls;
		var cls_new;
		var cls_init;
		var self;
		
		for (var i = 0; i < cls_mro.length; i++){
			cls_mro_cls = cls_mro[i];
			cls_new = cls_mro_cls.__new__;
			if (cls_new !== undefined) break;
		}
		
		if (cls_new === undefined){
			throw builtins.type.__call__([builtins.AttributeError, 'missing attribute "__new__"']);
		}
		
		self = cls_new(args, kwargs);
		
		if (builtins.getclass([self]) === cls){
			cls_init = cls.__init__;
			
			if (cls_init !== undefined){
				cls_init([self].concat(args.slice(1)), kwargs);
			}
		}
		
		return self;
	};
	
	builtins.type.mro = function (args, kwargs){
		/*
		 * return: list
		 */
		function _merge_mro(seqs){
			var res = [];
			var nonemptyseqs = null;
			var seq = null;
			var cand = null;
			var nothead = null;
			
			while (1){
				nonemptyseqs = [];
				
				for (var i = 0; i < seqs.length; i++){
					seq = seqs[i];
					
					if (seq.length > 0){
						nonemptyseqs.push(seq);
					}
				}
				
				if (nonemptyseqs.length === 0){
					return res;
				}
				
				for (var i = 0; i < nonemptyseqs.length; i++){
					seq = nonemptyseqs[i];
					cand = seq[0];
					nothead = [];
					
					for (var j = 0; j < nonemptyseqs.length; j++){
						seq = nonemptyseqs[j];
						
						if (seq.slice(1).indexOf(cand) != -1){
							nothead.push(seq);
						}
					}
					
					if (nothead.length > 0){
						cand = null;
					} else {
						break;
					}
				}
				
				if (cand === null){
					throw builtins.call([builtins.TypeError, 'cannot create a consistent method resolution']);
				}
				
				res.push(cand);
				
				for (var i = 0; i < nonemptyseqs.length; i++){
					seq = nonemptyseqs[i];
					
					if (seq[0] === cand){
						seq.shift();
					}
				}
			}
		}
		
		function _mro(C){
			var res = null;
			var segs = [[C]];
			var base = null;
			
			for (var i = 0; i < C.__bases__.length; i++){
				base = C.__bases__[i];
				segs.push(_mro(base));
			}
			
			
			for (var i = 0; i < C.__bases__.length; i++){
				base = C.__bases__[i];
				segs.push(base);
			}
			
			res = _merge_mro(segs);
			return res;
		}
		
		var base = args[0];
		var res = _mro(base);
		return res;
	};
	
	/*
	 * module class
	 */
	builtins.module.__getattr__ = function (args, kwargs){
		/*
		 * return: any object
		 * throw: AttributeError
		 */
		var module = args[0];
		var attr = args[1];
		return builtins.getattr([builtins, attr]);
	};
	
	builtins.module.__str__ = function (args, kwargs){
		/*
		 * return: str
		 * throw: TypeError
		 */
		var self = args[0];
		var cls = builtins.getclass([self]);
		return '<' + cls.__name__ + ' \'' + self.__name__ + '\'>';
	};
	
	/*
	 * null class
	 */
	builtins.NoneType = builtins.call([builtins.type, 'NoneType', [], {
		__new__: function (args, kwargs){
			var self = null;
			return self;
		},
		__str__: function (args, kwargs){
			return 'None';
		}
	}]);
	
	/*
	 * bool class
	 */
	builtins.bool = builtins.call([builtins.type, 'bool', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			return false;
		},
		__str__: function (args, kwargs){
			var self = args[0];
			var res = null;
			
			if (self){
				res = 'True';
			} else {
				res = 'False';
			}
			
			return res;
		},
	}]);
	
	/*
	 * int class
	 */
	builtins.int = builtins.call([builtins.type, 'int', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			return 0;
		},
		__str__: function (args, kwargs){
			var self = args[0];
			return self.toString();
		},
	}]);
	
	/*
	 * float class
	 */
	builtins.float = builtins.call([builtins.type, 'float', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			return 0.0;
		},
		__str__: function (args, kwargs){
			var self = args[0];
			return self.toString();
		},
	}]);
	
	/*
	 * bytes class
	 */
	builtins.bytes = builtins.call([builtins.type, 'bytes', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			return "";
		},
		__str__: function (args, kwargs){
			var self = args[0];
			return '"' + self + '"';
		},
	}]);
	
	/*
	 * str class
	 */
	builtins.str = builtins.call([builtins.type, 'str', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			return "";
		},
		__str__: function (args, kwargs){
			var self = args[0];
			return '\'' + self + '\'';
		},
	}]);
	
	/*
	 * tuple class
	 */
	builtins.tuple = builtins.call([builtins.type, 'tuple', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			var self = [];
			self.seql();
			return self;
		},
		__str__: function (args, kwargs){
			var self = args[0];
			var item = null;
			var reprs = [];
			var repr = null;
			var res = null;
			
			for (var i = 0; i < self.length; i++){
				item = self[i];
				repr = builtins.repr([item]);
				reprs.push(repr);
			}
			
			res = '(' + reprs.join(', ') + ')';
			return res;
		}
	}]);
	
	/*
	 * list class
	 */
	builtins.list = builtins.call([builtins.type, 'list', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			var self = [];
			return self;
		},
		__str__: function (args, kwargs){
			var self = args[0];
			var item = null;
			var n = null;
			var reprs = [];
			var res = null;
			
			for (var i = 0; i < self.length; i++){
				item = self[i];
				n = builtins.repr([item]);
				reprs.push(n);
			}
			
			res = '[' + reprs.join(', ') + ']';
			return res;
		},
	}]);
	
	/*
	 * set class
	 */
	builtins.set = builtins.call([builtins.type, 'set', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			var cls = args[0];
			var self = builtins.object.__new__([cls]);
			return self;
		},
		__str__: function (args, kwargs){
			var self = args[0];
			var reprs = [];
			var repr = null;
			var res = null;
			
			for (var attr in self){
				repr = builtins.repr([attr]);
				reprs.push(repr);
			}
			
			res = '{' + reprs.join(', ') + '}';
			return res;
		},
	}]);
	
	/*
	 * dict class
	 */
	builtins.dict = builtins.call([builtins.type, 'dict', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			return {};
		},
		__str__: function (args, kwargs){
			var self = args[0];
			var value = null;
			var reprs = [];
			var repr = null;
			var res = null;
			
			for (var attr in self){
				value = self[attr];
				repr = builtins.repr([attr]);
				repr += ': ';
				
				if (value === self){
					repr += '{...}';
				} else {
					repr += builtins.repr([value]);
				}
				
				reprs.push(repr);
			}
			
			res = '{' + reprs.join(', ') + '}';
			return res;
		},
	}]);
	
	/*
	 * code class
	 */
	builtins.CodeType = builtins.call([builtins.type, 'code', [], {
		__new__: function (args, kwargs){
			/* FIXME: */
			var cls = args[0];
			var self = builtins.object.__new__([cls]);
			return self;
		}
	}]);
	
	/*
	 * function class
	 */
	builtins.FunctionType = builtins.call([builtins.type, 'function', [], {
		__new__: function (args, kwargs){
			var cls = args[0];
			var self = args[1];
			return self;
		},
		__str__: function (args, kwargs){
			return '<function object>';
		},
		__call__: function (args, kwargs){
			var self = args[0];
			return self(args.slice(1), kwargs);
		},
		__get__: function (args, kwargs){
			var self = args[0];			/* function object */
			var instance = args[1];		/* bounding instance */
			var owner = args[2];		/* class; discarded */
			var res;
			
			if (!!instance){
				res = builtins.MethodType.__new__([builtins.MethodType, self, instance]);
			} else {
				res = self;
			}
			
			return res;
		},
	}]);
	
	/*
	 * method class
	 */
	builtins.MethodType = builtins.call([builtins.type, 'method', [], {
		__new__: function (args, kwargs){
			var cls = args[0];
			var func = args[1];
			var inst = args[2];
			var self = builtins.object.__new__([cls]);
			self.__func__ = func;
			self.__self__ = inst;
			return self;
		},
		__call__: function (args, kwargs){
			var self = args[0];
			var func = self.__func__;
			var inst = self.__self__;
			return func([inst].concat(args.slice(1)), kwargs);
		},
	}]);
	
	/*
	 * generator class
	 */
	builtins.GeneratorType = builtins.call([builtins.type, 'generator', [], {
	}]);
	
	/*
	 * exception classes
	 */
	builtins.BaseException = builtins.call([builtins.type, 'BaseException', [], {
		__new__: function (args, kwargs){
			var cls = args[0];
			var msg = args[1];
			var self = builtins.object.__new__([cls]);
			self.__msg__ = msg;
			return self;
		},
	}]);
	
	builtins.SystemExit = builtins.call([builtins.type, 'SystemExit', [builtins.BaseException], {}]);
	builtins.KeyboardInterrupt = builtins.call([builtins.type, 'KeyboardInterrupt', [builtins.BaseException], {}]);
	builtins.GeneratorExit = builtins.call([builtins.type, 'GeneratorExit', [builtins.BaseException], {}]);
	builtins.Exception = builtins.call([builtins.type, 'Exception', [builtins.BaseException], {}]);
		builtins.StopIteration = builtins.call([builtins.type, 'StopIteration', [builtins.Exception], {}]);
		builtins.Exception = builtins.call([builtins.type, 'Exception', [builtins.Exception], {}]);
		builtins.ArithmeticError = builtins.call([builtins.type, 'ArithmeticError', [builtins.Exception], {}]);
			builtins.FloatingPointError = builtins.call([builtins.type, 'FloatingPointError', [builtins.ArithmeticError], {}]);
			builtins.OverflowError = builtins.call([builtins.type, 'OverflowError', [builtins.ArithmeticError], {}]);
			builtins.ZeroDivisionError = builtins.call([builtins.type, 'ZeroDivisionError', [builtins.ArithmeticError], {}]);
		builtins.AssertionError = builtins.call([builtins.type, 'AssertionError', [builtins.Exception], {}]);
		builtins.AttributeError = builtins.call([builtins.type, 'AttributeError', [builtins.Exception], {}]);
		builtins.BufferError = builtins.call([builtins.type, 'BufferError', [builtins.Exception], {}]);
		builtins.EnvironmentError = builtins.call([builtins.type, 'EnvironmentError', [builtins.Exception], {}]);
			builtins.IOError = builtins.call([builtins.type, 'IOError', [builtins.EnvironmentError], {}]);
			builtins.OSError = builtins.call([builtins.type, 'OSError', [builtins.EnvironmentError], {}]);
		builtins.EOFError = builtins.call([builtins.type, 'EOFError', [builtins.Exception], {}]);
		builtins.ImportError = builtins.call([builtins.type, 'ImportError', [builtins.Exception], {}]);
		builtins.LookupError = builtins.call([builtins.type, 'LookupError', [builtins.Exception], {}]);
			builtins.IndexError = builtins.call([builtins.type, 'IndexError', [builtins.LookupError], {}]);
			builtins.KeyError = builtins.call([builtins.type, 'KeyError', [builtins.LookupError], {}]);
		builtins.MemoryError = builtins.call([builtins.type, 'MemoryError', [builtins.Exception], {}]);
		builtins.NameError = builtins.call([builtins.type, 'NameError', [builtins.Exception], {}]);
			builtins.UnboundLocalError = builtins.call([builtins.type, 'UnboundLocalError', [builtins.NameError], {}]);
		builtins.ReferenceError = builtins.call([builtins.type, 'ReferenceError', [builtins.Exception], {}]);
		builtins.RuntimeError = builtins.call([builtins.type, 'RuntimeError', [builtins.Exception], {}]);
			builtins.NotImplementedError = builtins.call([builtins.type, 'NotImplementedError', [builtins.RuntimeError], {}]);
		builtins.SyntaxError = builtins.call([builtins.type, 'SyntaxError', [builtins.Exception], {}]);
			builtins.IndentationError = builtins.call([builtins.type, 'IndentationError', [builtins.SyntaxError], {}]);
				builtins.TabError = builtins.call([builtins.type, 'TabError', [builtins.IndentationError], {}]);
		builtins.SystemError = builtins.call([builtins.type, 'SystemError', [builtins.Exception], {}]);
		builtins.TypeError = builtins.call([builtins.type, 'TypeError', [builtins.Exception], {}]);
		builtins.ValueError = builtins.call([builtins.type, 'ValueError', [builtins.Exception], {}]);
			builtins.UnicodeError = builtins.call([builtins.type, 'UnicodeError', [builtins.ValueError], {}]);
				builtins.UnicodeDecodeError = builtins.call([builtins.type, 'UnicodeDecodeError', [builtins.UnicodeError], {}]);
				builtins.UnicodeEncodeError = builtins.call([builtins.type, 'UnicodeEncodeError', [builtins.UnicodeError], {}]);
				builtins.UnicodeTranslateError = builtins.call([builtins.type, 'UnicodeTranslateError', [builtins.UnicodeError], {}]);
		builtins.Warning = builtins.call([builtins.type, 'Warning', [builtins.Exception], {}]);
			builtins.DeprecationWarning = builtins.call([builtins.type, 'DeprecationWarning', [builtins.Warning], {}]);
			builtins.PendingDeprecationWarning = builtins.call([builtins.type, 'PendingDeprecationWarning', [builtins.Warning], {}]);
			builtins.RuntimeWarning = builtins.call([builtins.type, 'RuntimeWarning', [builtins.Warning], {}]);
			builtins.SyntaxWarning = builtins.call([builtins.type, 'SyntaxWarning', [builtins.Warning], {}]);
			builtins.UserWarning = builtins.call([builtins.type, 'UserWarning', [builtins.Warning], {}]);
			builtins.FutureWarning = builtins.call([builtins.type, 'FutureWarning', [builtins.Warning], {}]);
			builtins.ImportWarning = builtins.call([builtins.type, 'ImportWarning', [builtins.Warning], {}]);
			builtins.UnicodeWarning = builtins.call([builtins.type, 'UnicodeWarning', [builtins.Warning], {}]);
			builtins.BytesWarning = builtins.call([builtins.type, 'BytesWarning', [builtins.Warning], {}]);
			builtins.ResourceWarning = builtins.call([builtins.type, 'ResourceWarning', [builtins.Warning], {}]);
	
	/*
	 * compiler
	 */
	builtins.compile_init = function (){
		var nodes = document.getElementsByTagName('script');
		var node;
		var module;
		var filename;
		var module_name;
		
		for (var i = 0; i < nodes.length; i++){
			node = nodes[i];
			
			if (node.type === 'text/python'){
				if (!!node.src){
					filename = node.src;
					module_name = filename;
					module = builtins.compile_module(filename);
				} else {
					module_name = '__main__';
					module = builtins.compile_module_from_text(node.innerHTML);
				}
				
				builtins.globals[module_name] = module;
			}
		}
	};
	
	builtins.compile_text = function (buffer){
		var code;
		return code;
	};
	
	builtins.compile_module = function (filename){
		var module;
		return module;
	};
	
	builtins.compile_module_from_text = function (text){
		var module;
		return module;
	};
	
	/*
	 * call when content loaded
	 */
	if (window.addEventListener){
		window.addEventListener("DOMContentLoaded", builtins.compile_init, false);
	} else if (window.attachEvent){
		window.attachEvent('onload', builtins.compile_init);
	} else if (window.addEventListener) {
		window.addEventListener("load", builtins.compile_init, false);
	}
})(window);
